﻿#include "Codecs.h"

#include <QDebug>
#include <QByteArray>

#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include <openssl/applink.c>
#include <openssl/evp.h> // base64

#include <openssl/aes.h>


// 自生成
QString pubKStr = "-----BEGIN PUBLIC KEY-----\n"
                  "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuW9TDILNqFc+y0czCEbK"
                  "Zgb7UUkqZIqcbOQgVSMmsaEtTAQcupWkz1/5AmM7UsRGl3wjaFCOr5eBAxbbE8/W"
                  "3Ylu13cOc9b5sXEZBd7hp6Za24ZyBBdHopUHbVu/RIfS70ddY+a1/kGiMgnygJib"
                  "hcg76k/vSwQF1qFVAPcjbvXeDZfAN5Qt4EDr+RyOUHEjbQsKXrGk9xb6VYrjHmu+"
                  "tgElACgFUpIaIctUUpWBm1QHxGtL07NWWGPABGomc0LkSlKByZpAS+YFKGHWVQZI"
                  "QempiSc8WQ4WIRUCSkWRVaMbor8ubVqMgPidfyFysH5GU39zscJK7BSPacs+WVln"
                  "XwIDAQAB\n"
                  "-----END PUBLIC KEY-----";

QString priKStr = "-----BEGIN PRIVATE KEY-----\n"
                  "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5b1MMgs2oVz7L"
                  "RzMIRspmBvtRSSpkipxs5CBVIyaxoS1MBBy6laTPX/kCYztSxEaXfCNoUI6vl4ED"
                  "FtsTz9bdiW7Xdw5z1vmxcRkF3uGnplrbhnIEF0eilQdtW79Eh9LvR11j5rX+QaIy"
                  "CfKAmJuFyDvqT+9LBAXWoVUA9yNu9d4Nl8A3lC3gQOv5HI5QcSNtCwpesaT3FvpV"
                  "iuMea762ASUAKAVSkhohy1RSlYGbVAfEa0vTs1ZYY8AEaiZzQuRKUoHJmkBL5gUo"
                  "YdZVBkhB6amJJzxZDhYhFQJKRZFVoxuivy5tWoyA+J1/IXKwfkZTf3OxwkrsFI9p"
                  "yz5ZWWdfAgMBAAECggEAS+kPQd1gLEw+5/9nHDnCjF7KTf3aLVty6r6+n6uqoOe+"
                  "v9IlIpq047HBoaEWvWydC1Flu0PTDX3DcgzOtT8NkhY7OlhCIdi80c9fM4CIttVK"
                  "d3iDvzn6eMtyRWncFEVX6902lXlyQjoDGGHrFIqki/Eq2eJMLtTgQPs9bY4+QqMO"
                  "1giUuY/LjjKo5syAhg2tzYjhfjFaOsKaeJ/GtBuiGBsP6Tvp+qh04R6PbRzat4gp"
                  "VgjMhlidAZYGDfdAXnY7GRpNkJzUHAeCThi/iv4k2MrPho5S9n+2SnT4rQ7nJKx/"
                  "YqNuJisRvRBVYR5Xpe6Hjxxo+ER+ol+riK3SeOF/1QKBgQD6qb+50t30xzbIBM7c"
                  "WXg1eW/4DyGaNvdERV2L3lmE4gKV+PCOzQ3brSUUWtn0eh+E+9n51RpzswIvPtjk"
                  "uFVrbwPYlvillSAuA1KoulG/97UzhKeGXxduRvYJK94OGGyikg2ggIDrMkzcS+GO"
                  "lq0XeCk4dDsBq5G84LxAp70lgwKBgQC9YgvDtj+wp8+kRtkoyEa2lhnd18hCjTMG"
                  "5ZfZ7inXNBsIOqBXxEaI74wfiYmyC1wS2lVwNoM2Pgytf5GHwLbFgBX4QnP8e8v6"
                  "QP5F4vrg0m5cAJj7nv4HaOHqshO2Sxhd9uETd0BVIHLGTkBnW/dKGla7SetRGzyZ"
                  "J8wiBMKr9QKBgH4vtjT10obHQuyufvSulbVVzOgztdbMlO3pfY5MAQTEQkj9aRx6"
                  "Xd+Y6nG0ysQoNnXb03/Rp99W3mKqYMXDhjzvRiBi4Al7aRfA1HvcUAszyCSHQ06B"
                  "0GnTj0hOrRqrcQTmQbXq+vtr9wVvrbz6XWjTNO0Wlh3hIRzUhfCGgNHBAoGAX2dg"
                  "E9Tg2uZ+1PwX89KIxPxyxW4p5VTqasQIrnGrRE1sfMXD9tLa0asThOOtqTLNzHWO"
                  "6vubOB0kF9cXAjNUH8R5zNn1QjE/Pa+2KJDC37VCgTiP2NpskGEGxku1YHUOLm/r"
                  "TPobP4ccKshK64bJ2dvQ64K+d9pnuaPx0wVg6G0CgYEA8aa+m5qFGsT9E4bJUCN4"
                  "cHwrPPabY1sBE78PdozqWUBfN/vnVqbNisF5bIkOpJp3EqvETq0oV5+f87C7Rlh/"
                  "dqwfZDAP2gUsWeHmv9QI/w4inIRnpw3RztDQz+JKPd/QgfSGCXTotTK9XXPMNhM0"
                  "KNeerMmbVy6YA0AV79Cc8A0="
                  "\n-----END PRIVATE KEY-----";

QString aesKey = "rzt82890758rzt..";


namespace {
struct BIOFreeAll { void operator()(BIO* p) { BIO_free_all(p); } };
}

QString Codecs::Base64EncodeBio(const QByteArray &bin)
{
    std::unique_ptr<BIO,BIOFreeAll> b64(BIO_new(BIO_f_base64()));
    BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
    BIO* sink = BIO_new(BIO_s_mem());
    BIO_push(b64.get(), sink);
    BIO_write(b64.get(), bin.data(), bin.size());
    BIO_flush(b64.get());
    const char* encoded;
    const long len = BIO_get_mem_data(sink, &encoded);
    QString res(QLatin1String(encoded, len));
    return res;
}

QByteArray Codecs::Base64DecodeBio(const QString &src)
{
    QByteArray sData = src.toUtf8();
    std::unique_ptr<BIO,BIOFreeAll> b64(BIO_new(BIO_f_base64()));
    BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
    BIO* source = BIO_new_mem_buf(sData.data(), -1); // read-only source
    BIO_push(b64.get(), source);
    const int maxlen = strlen(sData.data()) / 4 * 3 + 1;

    QByteArray decoded(maxlen, 0);
    const int len = BIO_read(b64.get(), decoded.data(), maxlen);
    decoded.resize(len);
    return decoded;
}

QString Codecs::Base64Encode(const QString &src)
{
    size_t sLen = (src.size() + 2) / 3 * 4;
    if (sLen == 0) {
        qDebug() << "encode::Base64Encode Failed";
        return "";
    }

    // 编码长度加‘/n’长度
    char *dstData = new char[sLen + 2]{'\0'};
    const QByteArray data = src.toLatin1();

    EVP_ENCODE_CTX *eCtx = EVP_ENCODE_CTX_new();
    EVP_EncodeInit(eCtx);
    int outLen = 0;
    int ret = EVP_EncodeUpdate(eCtx,
                               (unsigned char*)dstData,
                               &outLen,
                               reinterpret_cast<const unsigned char *>(data.constData()),
                               src.size());
    if (ret != 1) {
        qDebug() << "Base64 Encode Failed";
    }
    EVP_EncodeFinal(eCtx,
                    (unsigned char*)dstData,
                    &outLen
                    );
    EVP_ENCODE_CTX_free(eCtx);

    // Note: 使用此种一步到位的方法编码会有多余填充
    // int ret = EVP_EncodeBlock(reinterpret_cast<unsigned char *>(dstData),
    //                 reinterpret_cast<const unsigned char *>(data.constData()),
    //                 (int) sLen);

    // 此处删除末尾的换行符
    QString res;
    res.append(dstData);
    res = res.left(res.size() - 1);

    // 回收内存
    delete[] dstData;

    return res;
}

QString Codecs::Base64Decode(const QString &src)
{
    size_t srcLen = src.size();
    if (srcLen % 4 != 0) {
        qDebug() << "encode::Base64Decode Failed";
        return "";
    }

    size_t dstLen = (srcLen / 4) * 3;
    QByteArray resData(dstLen, '\0');
    QByteArray srcData = src.toLatin1();
    int ret = EVP_DecodeBlock((unsigned char *)resData.data(),
                              reinterpret_cast<const unsigned char *>(srcData.constData()),
                              (int)srcLen);
    if (ret == -1) {
        qDebug() << "encode::Base64Decode Decode Failed";
        return "";
    }

    int i = 0;
    while (src.at((int)--srcLen) == '=') {
        --ret;
        if (++i > 2) {
            return "";
        }
    }
    resData.resize(ret);
    return resData;
}

QByteArray Codecs::RSAEncrypt(const QString &src)
{
    if (pubKStr.isEmpty()) {
        qDebug() << "RSAEncrypt Failed: Public Key Is Empty!";
        return "";
    }

    QByteArray pkData = pubKStr.toLatin1();
    BIO *pkBIO = BIO_new(BIO_s_mem());
    BIO_write(pkBIO, pkData.constData(), pubKStr.size());
    EVP_PKEY *pKey = PEM_read_bio_PUBKEY(pkBIO, NULL, NULL, NULL);
    if (pKey == NULL) {
        qDebug() << "RSAEncrypt Failed: Public Key Is Invalid, Because " + getErrMsg();
        BIO_free_all(pkBIO);
        return "";
    }
    EVP_PKEY_CTX *pCtx = EVP_PKEY_CTX_new(pKey, NULL);
    if (pCtx == NULL) {
        qDebug() << "RSAEncrypt Failed: Can't Create PKEY_CTX, Because " + getErrMsg();
        BIO_free_all(pkBIO);
        EVP_PKEY_free(pKey);
        return "";
    }

    EVP_PKEY_encrypt_init(pCtx);
    if (EVP_PKEY_CTX_set_rsa_padding(pCtx, RSA_PKCS1_PADDING) <= 0)
        qDebug() << "RSAEncrypt Failed: Set padding method failed" << endl;

    QByteArray resData;
    // 缓冲区
    char *dstBuffer = nullptr;
    // 当前处理到的字符位置
    int strPos = 0;
    // 输出处理缓冲区的最大大小
    size_t outLen = 0;
    while (strPos < src.size()) {
        // 传入数据分块 (128Byte)
        QByteArray blockData = src.mid(strPos, 128).toLatin1();
        // 获取缓冲区的最大处理大小并分配空间
        int ret = EVP_PKEY_encrypt(pCtx, NULL, &outLen, (const unsigned char*)blockData.data(), blockData.size());
        dstBuffer = (char *)OPENSSL_malloc(outLen);
        // 开始加密
        ret = EVP_PKEY_encrypt(pCtx, (unsigned char*)dstBuffer, &outLen, (const unsigned char*)blockData.data(), blockData.size());
        if (ret == 0) {
            qDebug() << "encrypt failed";
            OPENSSL_free(dstBuffer);
            dstBuffer = nullptr;
            EVP_PKEY_free(pKey);
            EVP_PKEY_CTX_free(pCtx);
            BIO_free_all(pkBIO);
            return "";
        } else {
            resData.append(dstBuffer, outLen);
        }
        OPENSSL_free(dstBuffer);
        dstBuffer = nullptr;
        strPos += blockData.size();
    }

    // 非分块加密, 加密长短数据时会出现异常
    // if (!EVP_PKEY_encrypt(pCtx, (unsigned char*)dst, &blockSize, (unsigned char*)srcData.data(), src.size())) {
    //     qDebug() << "encrypt failed";
    //     delete[] dst;
    //     EVP_PKEY_free(pKey);
    //     EVP_PKEY_CTX_free(pCtx);
    //     return nullptr;
    // }

    EVP_PKEY_free(pKey);
    EVP_PKEY_CTX_free(pCtx);
    BIO_free_all(pkBIO);

    return resData;
}

QString Codecs::RSADecrypt(const QByteArray &src)
{
    if (pubKStr.isEmpty()) {
        qDebug() << "RSAEncrypt Failed: Public Key Is Empty!";
        return "";
    }

    const QByteArray pkData = priKStr.toLatin1();
    BIO *pkBIO = BIO_new(BIO_s_mem());
    BIO_write(pkBIO, pkData, priKStr.size());
    EVP_PKEY *pKey = PEM_read_bio_PrivateKey(pkBIO, NULL, NULL, NULL);
    if (pKey == NULL) {
        qDebug() << "RSAEncrypt Failed: Public Key Is Invalid, Because " + getErrMsg();
        BIO_free_all(pkBIO);
        return "";
    }
    EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pKey, NULL);
    if (ctx == NULL) {
        qDebug() << "RSAEncrypt Failed: Can't Create PKEY_CTX, Because " + getErrMsg();
        BIO_free_all(pkBIO);
        EVP_PKEY_free(pKey);
        return "";
    }

    EVP_PKEY_decrypt_init(ctx);
    if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
        qDebug() << "set padding method failed" << endl;

    QString res;
    char *dstBuffer = nullptr;
    int pos = 0;
    size_t outLen = 0;
    while (pos < src.size()) {
        // 传入数据分块 (256Byte)
        QByteArray blockData = src.mid(pos, 256);
        // 获取缓冲区的最大处理大小并分配空间
        int ret = EVP_PKEY_decrypt(ctx, NULL, &outLen, (const unsigned char*)blockData.data(), blockData.size());
        dstBuffer = (char *)OPENSSL_malloc(outLen);
        // 开始解密
        ret = EVP_PKEY_decrypt(ctx, (unsigned char*)dstBuffer, &outLen, (const unsigned char*)blockData.data(), blockData.size());
        if (ret == 0) {
            qDebug() << "encrypt failed";
            OPENSSL_free(dstBuffer);
            dstBuffer = nullptr;
            EVP_PKEY_free(pKey);
            EVP_PKEY_CTX_free(ctx);
            BIO_free_all(pkBIO);
            return "";
        } else {
            res.append(QLatin1String(dstBuffer, outLen));
        }
        OPENSSL_free(dstBuffer);
        dstBuffer = nullptr;
        pos += blockData.size();
    }

    // if (!EVP_PKEY_decrypt(ctx,
    //                       (unsigned char *)dst,
    //                       &blockSize,
    //                       (unsigned char *)src.constData(),
    //                       src.size())) {
    //     qDebug() << "decrypt failed";
    //     delete[] dst;
    //     EVP_PKEY_free(pKey);
    //     EVP_PKEY_CTX_free(ctx);
    //     return "";
    // }

    EVP_PKEY_free(pKey);
    EVP_PKEY_CTX_free(ctx);
    BIO_free_all(pkBIO);

    return res;
}

QByteArray Codecs::AES_ECBEncrypt(const QString &src)
{
    if (src.isEmpty())
        return NULL;

    // 初始化CIPHER
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    if (!ctx) {
        qDebug() << "Failed to Create Cipher Context!";
        return NULL;
    }
    EVP_CIPHER_CTX_init(ctx);
    // 使能密钥
    QByteArray keyBytes = aesKey.toLatin1();
    if (EVP_EncryptInit_ex(
            ctx, EVP_aes_128_ecb(),
            NULL, (const uchar*)keyBytes.data(), NULL
            ) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        qDebug() << "Failed Initial Encryption!";
        return NULL;
    }

    int length = 0;
    QByteArray cipherBytes;
    QByteArray rawBytes = src.toLatin1();
    cipherBytes.resize(rawBytes.size() + EVP_CIPHER_block_size(EVP_aes_128_ecb()));

    // 开始AES加密
    if (EVP_EncryptUpdate(
            ctx,
            (uchar*)cipherBytes.data(), &length,
            (const uchar*)rawBytes.data(), rawBytes.size()
            ) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        qDebug() << "Failed to Encrypt Data!";
        return NULL;
    }

    // 处理最后加密部分
    int padding_len = 0;
    if (EVP_EncryptFinal_ex(ctx, (uchar*)cipherBytes.data() + length, &padding_len) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        qDebug() << "Failed to Finalize Encryption!";
        return NULL;
    }

    cipherBytes.resize(length + padding_len);
    EVP_CIPHER_CTX_free(ctx);

    return cipherBytes;
}

QString Codecs::AES_ECBDecrypt(const QByteArray &src)
{
    if (src.isEmpty())
        return NULL;

    QByteArray cipherBytes;
    cipherBytes.resize(src.size());

    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    if (!ctx) {
        qDebug() << "Failed to Create Cipher Context!";
        return NULL;
    }
    EVP_CIPHER_CTX_init(ctx);

    if (EVP_DecryptInit_ex(
            ctx, EVP_aes_128_ecb(),
            NULL, (const uchar*)aesKey.toLatin1().data(),
            NULL
            ) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        qDebug() << "Failed to Initial Encryption!";
        return NULL;
    }

    int length = 0;
    if (EVP_DecryptUpdate(
            ctx,
            (uchar*)cipherBytes.data(), &length,
            (const uchar*)src.data(), cipherBytes.size()
        ) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        qDebug() << "Failed to Decrypt Data!";
        return NULL;
    }

    int padding_len = 0;
    if (EVP_DecryptFinal_ex(ctx, (uchar*)cipherBytes.data() + length, &padding_len) != 1) {
        EVP_CIPHER_CTX_free(ctx);
        qDebug() << "Failed to Finalize Encryption!";
        return NULL;
    }

    cipherBytes.resize(length + padding_len);
    EVP_CIPHER_CTX_free(ctx);

    return cipherBytes;
}

QString Codecs::getErrMsg()
{
    // 错误处理
    QString msg;
    auto retErr = ERR_get_error();
    if (retErr != 0) {
        char *buf = new char[2048]{0};
        msg = ERR_error_string(retErr, buf);
        delete[] buf;
    }
    return msg;
}
